package com.tool.grab.vpn.header;


/**
 * IP报头解析说明 及收集的资料
 * JAVA 代码中的基础数据类型取值及占位统计
 * 1 byte(字节) = 8 bit（位）
 * 1个byte类型的数占1个字节  取值 十进制 -128~127  二进制00000000~ 11111111
 * 1个short类型的数占2个字节 取值 十进制 -32768~32767 二进制 00000000 00000000~11111111 11111111
 * 1个int类型的数占4个字节 取值 十进制 -2147483648～2147483647 二进制 00000000 00000000 00000000 00000000~11111111 11111111 11111111 11111111
 * 1个long类型的数占8字节 取值 十进制 -9223372036854775808~9223372036854775807
 * 1个float类型的数占4字节 取值 十进制 绝对值约1.4E-45~3.4E38
 * 1个double类型的数占8字节 取值 十进制 绝对值约4.9E-324~1.8E308
 * 1个char（字符）类型的数占2字节 取值 十进制 0~65535  UTF-8编码中 1个汉字占2字符 1个英文字符占一个字符
 * 1个boolean类型的数占1字节 取值 0~1 0假（false） 1真(true)
 *
 * 数据链路层
 * 路由层报文格式
 *   ｜　－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 *   ｜                                        48bit目的MAC地址     6字节                                    ｜
 *   ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 *   ｜                                        48bit源MAC地址       6字节                                    ｜
 *   ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 *   ｜                                        16bit类型            2字节                                    ｜
 *   ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 *   ｜                                        368bit~11000bit数据  46字节~1500字节                          ｜
 *   ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 *   ｜                                        32bit CRC            4字节                                    ｜
 *   ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 *
 *   链路层最小传输单元为 64字节   所以数据最小传输为 64 - 6 - 6 - 2 - 4 = 46 字节
 *   链路层最大传输单元为 1500字节
 *
 * 网络层
 * IP报文格式
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 * ｜    4 bit     ｜    4 bit      ｜      8 bit       ｜                   16 bit                          ｜
 * ｜    版本号    ｜    头部长     ｜     服务类型     ｜           总长度 （total length）                 ｜I
 * ｜              ｜ （单位4 byte）｜  　 （tos）      ｜                （单位1 byte）    　               ｜P
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜报
 * ｜  　　　　　　　　           16 bit                ｜3 bit ｜　　       13 bit                          ｜文
 * ｜                             序列号                ｜ 标志 ｜        片偏移（offset）                   ｜首
 * ｜                           （sequence）            ｜DF\MF ｜         （单位8 byte）　　                ｜部
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜默
 * ｜            8 bit              ｜       8 bit      ｜　　　　　　      16 bit                           ｜认
 * ｜           生存时间            ｜      上层协议    ｜                首部校验和                         ｜长
 * ｜     （TTL time to live）　    ｜     （protocol） ｜                （checksum）                       ｜为
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜2
 * ｜                                                 32 bit                                                 ｜0
 * ｜                                          源IP地址（local address ）                                   ｜个
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜字
 * ｜                                                 32 bit                                                 ｜节
 * ｜                                        目的IP地址（remote address）                               ｜
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 * ｜                                            选项和填充（若有）                                          ｜
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 * ｜                                                  数据                                                  ｜
 * ｜－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－｜
 *
 **/

public class IPPacket extends Packet{
    private final static String TAG = IPPacket.class.getSimpleName();

    private final static int VERSION_BIT = 0;
    private final static int HEADER_LENGTH_BIT = 0;
    private final static int TOS_BIT = 1;
    private final static int TOTAL_LENGTH_BIT = 2;
    private final static int SEQUENCE_BIT = 4;
    private final static int TAG_BIT = 6;
    private final static int OFFSET_BIT = 6;
    private final static int LIVE_BIT = 8;

    private final static int PROTOCL_BIT = 9;
    public final static int PROTOCL_ICMP = 1;
    public final static int PROTOCL_IGMP = 2;
    public final static int PROTOCL_OSPF = 89;
    public final static int PROTOCL_TCP = 6;
    public final static int PROTOCL_UDP = 17;

    private final static int HEADER_SUM_BIT = 10;
    private final static int LOCAL_IP_BIT = 12;
    private final static int REMOTE_IP_BIT = 16;


    public IPPacket(byte[] bytes, int... parameters){
        super(bytes,parameters);
    }

    /**
     * 获取IP报文协议版本
     * 协议版本
     *     在IP报文第0个字节中前4位 占1/2字节 共4位
     * @return
     */
    public int getVersion(){
        return bytes[VERSION_BIT] >> 4 & 0xFF;
    }

    /**
     * 获取IP报文头长度
     * 报头长度
     *     在IP报文第0个字节中后4位 占1/2字节 共4位
     *     最小表示为 0000(十进制0) 最大表示为 1111(十进制15) 单位为4字节
     *     也就是报头 最小为0 * 4字节0 * 4 * 8 = 0位，最大为15 * 4字节15 * 4 * 8 = 480位
     *     一般报头长度是0101（十进制5），也就是5 * 4 = 20字节
     * @return
     */
    public int getHeaderLength(){
        return (bytes[HEADER_LENGTH_BIT] & 0xF) * 4;
    }

    /**
     * 获取TOS服务字段
     * 服务类型
     *     在IP报文中第1个字节 占1字节 共8位
     *     一般情况不使用 也没查到具体有什么用
     * @return
     */
    public int getTos(){
        return bytes[TOS_BIT] & 0xFF;
    }

    /**
     * 获取IP报文总长度
     * 数据报总长度
     *     在IP报文中第2、3字节 占2字节 共16位 单位为1字节
     *     标注没有分片前整个IP请求的总长度（包括 数据 和 报头）
     * @return
     */
    public int getTotalLength(){
        return bytes[TOTAL_LENGTH_BIT] & 0xFF << 8  | bytes[TOTAL_LENGTH_BIT + 1] & 0xFF;
    }

    /**
     * 获取序列码
     * 序列码
     *     在IP报文中第4个和第5个字节 占2字节 共16位
     *     分片请求时用作同IP请求标识
     *     开始发送数据时由发送方生成。标识发送方发送的每一个数据报，如果发送的数据报未发生分片，则此值依次加1，如果发生了分片，分片后的各个数据报使用同一个序列号。
     * @return
     */
    public int getSequence(){
        return bytes[SEQUENCE_BIT] & 0xFF << 8 | bytes[SEQUENCE_BIT + 1] & 0xFF;
    }

    /**
     * 获取标志位
     * 分片标志
     *     在IP报文中第6个字节的前3位 占3/8字节 共3位
     *     第一位保留，未使用。
     *     第二位是DF（Don’t Fragment），如果为1，表示未发生分片;如果为0，表示发生了分片。
     *     第三位是MF（More Fragment），如果为1，表示发生了分片，并且除了分片出的最后一个报文中此标志为0，其余报文中此标志均为1
     *     该标志位有以下几个可能的值
     *      001（十进制1）：发生分片且后续还有分片
     *      000（十进制0）：发生分片且这是最后一个分片
     *      010（十进制2）：不能分片
     * @return
     */
    public int getTag(){
        return bytes[TAG_BIT] >> 5 & 3;
    }

    /**
     * 是否发生分片
     * 1 为未发生分片 返回false
     * 0 为发生了分片 返回true
     * DF Don't Fragment
     * @return
     */
    public boolean isDF(){
        if((getTag() >> 1) == 1){
            return false;
        }else{
            return true;
        }
    }

    /**
     * 是否还有分片
     * 如果未发生分片直接返回false
     * 如果发生分片且后面还有分片返回true
     * 如果发生分片且后面没有分片了返回false
     * MF  More Fragment
     * @return
     */
    public boolean isMF(){
        if(isDF() && (getTag() & 1) == 1){
            return true;
        }else{
            return false;
        }
    }

    /**
     * 获取写入报文中的偏移值
     * 片偏移量 单位 8 byte
     *     在IP报文中第6个字节的后5位和第7个字节 占13/8字节 共13位
     *     标识分片相对于原始IP数据报开始处的偏移。只在分片传输时起作用
     *     链路层MTU（Maximum Transmission Unit 最大传输单元）为1500
     *     如果IP报文总长度超过这个数值，就会产生IP分片
     *      下面是计算公式
     *      假设链路层传输时分片数量为 n
     *      IP报头长度为20字节（实际传输时默认为20）
     *      IP报文总长度为 m
     *      分片后的片偏移为y (不管有没有分片，首个IP分片的片偏移肯定是0 如果有分片n肯定是大于1的)
     *      n = m > 1500  ? (m - m % 1500) / 1500 + 1 ：1
     *      y = (1500 - 20) * (n - 1) / 8
     *      可能会觉得懵逼 为什么还会除一个8 这是因为片偏移的单位是 8 byte 这里得到的值是最后写到报文中片偏移的值
     *      我看过一个文档上面写的有个这样的描述：从0位到1479位是IP报数据位 当时不明白这个1479是怎么来的
     *      后来仔细想想   这玩意是从下标读数来的 数组中读数是从0位开始到 数组长度 - 1 结束。
     *      也就是 MTU - 报头长度 - 1 => 1500 - 20 - 1 = 1479
     *      从0位数到1479位 正好是1500个位置。
     * @return
     */
    public int getOffset(){
       return bytes[OFFSET_BIT] & 0x1f << 8 | bytes[OFFSET_BIT + 1] & 0xFF;
    }

    /**
     * 获取实际的偏移量
     * @return
     */
    public int getOffsetByte(){
        return getOffset() * 8;
    }

    /**
     * 获取生命周期
     * 生存时间
     *     在IP报文中第8个字节 占1字节 共8位
     *     表示生存周期，每经过一个路由值减一，防止路由环路。（windows系统下默认值为128 二进制 10000000）
     *     例如 路由A发数据给B B又转发给A 值依次衰减，值为0时或主动停止时停止转发
     * @return
     */
    public int getLive(){
        return bytes[LIVE_BIT] & 0xFF;
    }

    /**
     * 获取上层协议
     * 上层协议
     *     在IP报文中第9个字节 占1字节 共8位
     *     该值标识上层协议
     *     其中常见的值有
     *      ICMP协议 十进制 1 二进制 00000001
     *      IGMP协议 十进制 2 二进制 00000010
     *      OSPF协议 十进制 89 二进制 01011001
     *      TCP协议 十进制 6 二进制 00000110
     *      UDP协议 十进制 17 二进制 00010001
     * @return
     */
    public int getProtocl(){
        return bytes[PROTOCL_BIT] & 0xFF;
    }

    /**
     * 获取头部校验和
     * 头部校验和
     *     在IP报文中第10、11个字节 占2字节 共16位
     *     该值是对整个数据包的包头进行的校验
     * @return
     */
    public int getHeaderSum(){
        return bytes[HEADER_SUM_BIT] & 0xFF << 8 | bytes[HEADER_SUM_BIT + 1] & 0xFF;
    }

    /**
     * 获取本地IP地址 int类型
     * 源IP地址
     *     在IP报文中第12、13、14、15个字节 占4个字节（32位）
     *     表示本地IP地址（发送方MAC地址 也就是发送方的物理硬件地址） 那么地址掩码又是怎么回事？
     *     因为一个字节最大表示数为255 所以我们看到的IP地址通常写为x.x.x.x的格式(x为0-255的十进制数 转换为二进制数为00000000~11111111)
     * @return
     */
    public int getLocalIpInt(){
        return    bytes[LOCAL_IP_BIT] & 0xFF << 24
                | bytes[LOCAL_IP_BIT + 1] & 0xFF << 16
                | bytes[LOCAL_IP_BIT + 2] & 0xFF << 8
                | bytes[LOCAL_IP_BIT + 3] & 0xFF;
    }

    /**
     * 获取本地IP地址 String类型
     * x.x.x.x格式
     * @return
     */
    public String getLocalIpString(){
        return (bytes[LOCAL_IP_BIT] & 0xFF)
                + "." + (bytes[LOCAL_IP_BIT + 1] & 0xFF)
                + "." + (bytes[LOCAL_IP_BIT + 2] & 0xFF)
                + "." + (bytes[LOCAL_IP_BIT+3] & 0xFF);
    }

    /**
     * 获取远程IP地址 int类型
     * 目的IP地址
     *     在IP报文中第16、17、18、19个字节 占4个字节（32位）
     *     远程IP地址（接收方MAC地址 也就是接收方的物理硬件地址）
     *     因为一个字节最大表示数为255 所以我们看到的IP地址通常写为x.x.x.x的格式(x为0-255的十进制数 转换为二进制数为00000000~11111111)
     * @return
     */
    public int getRemoteIpInt(){
        return    bytes[REMOTE_IP_BIT] & 0xFF << 24
                | bytes[REMOTE_IP_BIT + 1] & 0xFF << 16
                | bytes[REMOTE_IP_BIT + 2] & 0xFF << 8
                | bytes[REMOTE_IP_BIT + 3] & 0xFF;
    }

    /**
     * 获取远程IP地址 String类型
     * x.x.x.x格式
     * @return
     */
    public String getRemoteIpString(){
        return (bytes[REMOTE_IP_BIT] & 0xFF)
                + "." + (bytes[REMOTE_IP_BIT + 1] & 0xFF)
                + "." + (bytes[REMOTE_IP_BIT + 2] & 0xFF)
                + "." + (bytes[REMOTE_IP_BIT + 3] & 0xFF);
    }

    /**
     * 获取数据报中有效的数据字节 不包含报头
     * @param effectiveLength 一个包含头部的有效长度 一般是数据流中读取到数据的长度
     * @return byte[] 返回一个除报头以外所有有效数据的字节数组
     */
    public byte[] getDataBytes(int effectiveLength){

        if(effectiveLength == 0 || effectiveLength > bytes.length) effectiveLength = bytes.length;

        // 得到有效数据在字节组中开始的位置
        int start = getHeaderLength();
        // 得到有效数据的长度
        int dataLength = effectiveLength - start;

        byte[] data = new byte[dataLength];

        for(int i = 0; i < data.length; i++){
            data[i] = bytes[start + i];
        }
        return data;
    }


    /**
     * 获取数据报中所有有效数据集合 包含报头
     * @return
     */
    public byte[] getAllBytes(int effectiveLength){

        if(effectiveLength == 0 || effectiveLength > bytes.length) effectiveLength = bytes.length;

        if(effectiveLength == bytes.length) return bytes;

        byte[] data = new byte[effectiveLength];

        for(int i = 0; i < data.length;i++){
            data[i] = bytes[i];
        }
        return data;
    }


    /**
     * 检查头部校验和
     * 很无语这个校验和的算法模式为什么会这样设计
     * 直接取每个字节中表示的十进制数相加不就得了
     * 还非得用16位来表示肯定会溢出的校验和
     * 这个方法还没有搞明白
     */
    public boolean checkSum(){
        return (short) ~getSum() == 0;
    }

    /**
     * 校验求和
     * @return
     */
    private int getSum(){
        int headerLength = getHeaderLength();
        int sum = 0;

        // 两字节一组依次计算16位和得到累加值
        for(int i = offset; i < offset + headerLength; i += 2){
            if(i >= bytes.length) return 0;
            sum +=  bytes[i] << 8 | bytes[i + 1];
        }

        // 如果报头为奇数 那么最后一个字节的和也需要加上
        if((offset + headerLength) % 2 > 0 ){
            sum += bytes[offset + headerLength - 1] << 8;
        }

        //这里计算校验和16位之上是否有进位 若有进位则循环折叠求和 直到16位之上没有进位
        while ((sum >> 16) > 0){
            sum = (sum & 0xFFFF) + ( sum >> 16) ;
        }

        return sum;
    }
    /**
     * 设置完参数后重新计算校验和并设置
     */
    public void refreshCheckSum(){
        bytes[HEADER_SUM_BIT] = 0;
        bytes[HEADER_SUM_BIT + 1] = 0;
        int sum = getSum();
        bytes[HEADER_SUM_BIT] = (byte) (~sum >> 8);
        bytes[HEADER_SUM_BIT + 1] = (byte)(~sum);
    }

    public void setLocalIp(int ip){
        bytes[LOCAL_IP_BIT] = (byte) (ip >> 24);
        bytes[LOCAL_IP_BIT + 1] = (byte) (ip >> 16);
        bytes[LOCAL_IP_BIT + 2] = (byte) (ip >> 8);
        bytes[LOCAL_IP_BIT + 3] = (byte) (ip);
    }

    public void setRemoteIp(int ip){
        bytes[REMOTE_IP_BIT] = (byte) (ip >> 24);
        bytes[REMOTE_IP_BIT + 1] = (byte) (ip >> 16);
        bytes[REMOTE_IP_BIT + 2] = (byte) (ip >> 8);
        bytes[REMOTE_IP_BIT + 3] = (byte) (ip);
    }



}
